package cn.kioye.mvc;

import cn.kioye.mvc.annotation.Autowired;
import cn.kioye.mvc.annotation.RequestMapping;
import cn.kioye.mvc.annotation.RestController;
import cn.kioye.mvc.annotation.Service;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.text.BreakIterator;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

/**
 * @author kioye
 * @date 2021-06-07 下午 08:14
 */
public class DispatchServlet extends HttpServlet {

    Map<Object, Object> iocMap = null;
    Map<String, Method> urlHandlerMap = null;

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println(config);
        // load config
        // get contextConfigLocation
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        String scanPackage = null;
        try(InputStream input = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation)){
            Properties properties = new Properties();
            properties.load(input);
            scanPackage = properties.getProperty("scan-package");

        } catch (IOException e) {
            e.printStackTrace();
        }
        if (scanPackage == null || scanPackage.isEmpty()) {
            throw new RuntimeException("scan-package path not fund!");
        }
        // scan class
        List<String> classFiles = scanPackageClass(scanPackage);
        // init obj to ioc
        iocMap = initObjToIOC(classFiles);
        // inject to annotation
        inject(iocMap);
        // mapping url -> service
        urlHandlerMap = bindUrlMap(iocMap);


    }

    private Map<String, Method> bindUrlMap(Map<Object, Object> iocMap) {
        Map<String, Method> urlHandlerMap = new HashMap<>();
        iocMap.forEach((k,v)->{
            if (k instanceof Class) {
                RestController annotation = ((Class<?>) k).getAnnotation(RestController.class);
                if (annotation!=null) {
                    RequestMapping urlMappingAnnotation = ((Class<?>) k).getAnnotation(RequestMapping.class);
                    String baseUrlPrefix = "";
                    if (urlMappingAnnotation != null) {
                        String[] value = urlMappingAnnotation.value();
                        if (value[0] != null) {
                            baseUrlPrefix = value[0].trim();
                        }
                    }
                    // find method
                    for (Method declaredMethod : ((Class<?>) k).getDeclaredMethods()) {
                        RequestMapping methodUrlMapping = declaredMethod.getAnnotation(RequestMapping.class);
                        if (methodUrlMapping != null) {
                            String[] value = methodUrlMapping.value();
                            if (value[0] != null) {
                                urlHandlerMap.put("/"+baseUrlPrefix+"/"+value[0].trim(), declaredMethod);
                            }else {
                                urlHandlerMap.put("/"+baseUrlPrefix, declaredMethod);
                            }
                        }
                    }
                }
            }
        });
        return urlHandlerMap;
    }

    private void inject(Map<Object, Object> iocMap) {
        iocMap.forEach((k,v)->{
            Field[] declaredFields = v.getClass().getDeclaredFields();

            for (Field declaredField : declaredFields) {
                Autowired annotation = declaredField.getAnnotation(Autowired.class);
                if (annotation != null) {
                    // inject
                    String value = annotation.value();
                    System.out.println("need inject name:"+declaredField.getName()+" value:"+value);
                    Object o = iocMap.get(declaredField.getName());
                    if (o == null) {
                        // try class
                        o = iocMap.get(declaredField.getType());
                        if (o == null&&declaredField.getType().isInterface()) {
                            // find sub class
                            for (Object o1 : iocMap.keySet()) {
                                if (o1 instanceof Class&&declaredField.getType().isAssignableFrom((Class<?>) o1)) {
                                    o = iocMap.get(o1);
                                }
                            }
                        }
                    }

                    if (o != null) {
                        declaredField.setAccessible(true);
                        try {
                            declaredField.set(v,o);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

    }

    private Map<Object,Object> initObjToIOC(List<String> classFiles) {
        Map<Object, Object> resultMap = new HashMap<>();
        for (String classFile : classFiles) {
            Class<?> clazz = null;
            try {
                clazz = Class.forName(classFile);
                // weather annotation
                if (clazz.isAnnotation()||clazz.isEnum()) {
                    continue;
                }
                Annotation declaredAnnotation = clazz.getDeclaredAnnotation(RestController.class);
                if (declaredAnnotation == null) {
                    declaredAnnotation = clazz.getDeclaredAnnotation(Service.class);
                }
                if (declaredAnnotation!=null) {
                    Object o = clazz.newInstance();
                    String substring = classFile.substring(classFile.lastIndexOf('.')+1);
                    char[] chars = substring.toCharArray();
                    if (chars[0] > 'A' && chars[0] < 'Z') {
                        chars[0] +=32;
                    }
                    resultMap.put(new String(chars), o);
                    resultMap.put(clazz, o);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
        return resultMap;
    }

    public List<String> scanPackageClass(String path) {
        List<String> classFiles = new ArrayList<>();
        URL scanPathURL = this.getClass().getClassLoader().getResource("/" + path.replace(".", "/"));
        if (scanPathURL != null) {
            File fileDir = new File(scanPathURL.getPath());
            for (File file : fileDir.listFiles()) {
                if (file.isDirectory()) {
                    classFiles.addAll(scanPackageClass(path+"."+file.getName()));
                }else if (file.isFile()){
                    classFiles.add(path+"."+file.getName().replace(".class",""));
                }
            }
        }
        return classFiles;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        // handler request
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        String requestURI = request.getRequestURI();
        AtomicBoolean hasHandler = new AtomicBoolean(false);
        urlHandlerMap.forEach((k,v)->{
            if (Pattern.matches(k,requestURI)) {
                // 调它
                Object invoke = null;
                try {
                    invoke = v.invoke(iocMap.get(v.getDeclaringClass()));
                    System.out.println("result:"+invoke);
                    response.getWriter().write(invoke.toString());
                    hasHandler.set(true);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return;
            }
        });
        if (!hasHandler.get()) {
            response.getWriter().write("not found!");
        }
    }
}
